/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Query Module:	Gimme's CSS Querying Engine
*
*	Requires:
*	Nothing
*
*/

var Gimme = {};
(function()
{
	// the querying engine
	var Engine = new function()
	{
		// private variables
		var nthHashHash = {};
		var DOMBool = new function()
		{
			this.val = -2;
			this.t = 0;
			this.f = 0;
			this.inc = function()
			{
				this.val += 2;
				this.t = this.val + 1;
				this.f = this.val;
			};
		};

		// public methods
		this.query = query;
		this.processSelector = processSelector;
		this.nthCacheContains = nthCacheContains;

		// member functions
		function query(_s, _node)
		{
	
			// If the passed in selector is "false-like" return an empty array
			if (!_s)
			{
				return [];
			}
			// If the passed in selector /is/ an array, just give it back
			if (_s instanceof Array)
			{
				return _s;
			}
			// Finally, if it's anything other than a string, return it as the sole element in an array (generally, I expect this to be a DOM element)
			if (typeof _s !== 'string')
			{
				return [ _s ];
			}
			
			// use querySelectorAll when supported since it will be faster
			if (typeof document.querySelectorAll !== 'undefined')
			{
			    // Gimme supports partial queries (like ">.dog" or "~.cat").
			    // querySelectorAll doesn't and probably won't ever, but that's ok. Since a try/catch is required anyway
			    // we'll allow it to try (maybe someday it will support it) and if it fails, it will fallback to Gimme
				try
				{
					return makeArray((_node || document).querySelectorAll(_s));
				}
				catch (everything)
				{
					//alert(everything.message);
				}
			}		

			// Reset the nthHashHash
			nthHashHash = {};

			// Invalidate the DOMBool (expando properties that may have been added to some DOM elements)
			DOMBool.inc();

			// Parse the selector, supply a _node (usually the acting root node, but could also be the lhs of a partial selector), and go fetch some elements!
			return select(CSSParser.parseSelector(_s), _node);
		}

		function select(_o, _node)
		{
			var s = _o.selectors
			,hints = _o.hints
			,anchor = hints.anchor;
			_node = _node || document.documentElement;

			// if the anchor's .elem property is null, it means a needed ID in the selector was not found in the DOM, therefore failure is imminent
			if (anchor.elem === null)
			{
				return [];
			}

			if (hints.isPartialQuery)
			{
				s.unshift(CSSParser.createReferenceSelector(_node));
				_node = _node.parentNode;
			}
			else if (anchor.elem !== -1 && s.length > 1)
			{
				// if the anchor's .elem property is /not/ -1, it means that a useful "anchor" was found which can be used to (potentially) speed up the query.
				// note however, that we only care about the anchor element for queries that contains at least one combinator (i.e. selectorParts.length > 1)
				_node = anchor.elem;
			}

			// the parser may have been able to retrieve an initial collection of elements, in which case we should use it (this is mostly to speed up direct-child (>) queries)
			if (hints.initialCollection !== null)
			{
				elems = filterElements(hints.initialCollection, s[s.length - 1]);
			}
			// otherwise, execute a simpleGimme to get intial collection of elems to be filtered
			else
			{
				elems = simpleGimme(s[s.length - 1], _node);
			}

			if (s.length === 1)
			{
				return elems;
			}

			if (s.length === 3)
			{
				if (anchor.isIdeal)
				{
					return elems;
				}
				else
				{
					// TODO:  deal with non 3-piece ideal anchor here
				}
			}

			var counter = 0;
			var i, j, len = elems.length;
			var combinatorFunctions = selectorRules.combinator;
			var lhs, rhs, cursor, combinator, previousCombinator = null;
			var matched = [];

			matchSeeker:
			for (i = 0; i < len; i++)
			{
				j = s.length - 2;
				combinator = s[j];
				cursor = rhs = elems[i];

				while (combinator)
				{
					lhs = s[j - 1];

					if (!combinatorFunctions[combinator](lhs, cursor))
					{
						// If previousCombinator was descendant while the current combinator is not (descendant), it means that failure of the combinatorFunction is not conclusive.
						// Therefore, we need to cycle the cursor back to its .parentNode and continue the while loop without advancing any further through the selectors array, s
						// Note however, that we only want to do this in the event that the cursor's .parentNode isn't null (meaning that we've hit the top of the document)
						if (cursor.parentNode && previousCombinator === ' ' && combinator !== ' ')
						{
							cursor = cursor.parentNode;
							continue;
						}
						else
						{
							continue matchSeeker;
						}
					}

					cursor = lhs.cursor;
					j -= 2;
					previousCombinator = combinator;
					combinator = j < 1 ? null : s[j];
				}

				matched[counter++] = rhs;
			}

			return matched;
		}

		function processSelector(_ss, _elem)
		{
			if (!_elem)
			{
				return false;
			}
			if (_ss.refersTo)
			{
				return _ss.refersTo === _elem;
			}

			var inProps = _ss.inProps
			,id = inProps.id
			,tag = inProps.tag
			,classes = inProps.classes
			,attributes = inProps.attributes
			,pseudos = inProps.pseudos;

			var inRules = selectorRules.inRules;
			var exRules = selectorRules.exRules;

			if (id && _elem.id !== id
				|| tag && _elem.tagName !== tag
				|| classes.length > 0 && !inRules['.'].processFn(_elem, classes)
				|| attributes.length > 0 && !inRules['['].processFn(_elem, attributes)
				|| pseudos.length > 0 && !inRules[':'].processFn(_elem, pseudos))
			{
				return false;
			}

			var key, ruleName, exProps = _ss.exProps;
			for (key in exRules)
			{
				ruleName = exRules[key].name;
				if (exProps[ruleName] && !exRules[key].processFn(_elem, exProps[ruleName]))
				{
					return false;
				}
			}

			return true;
		}

		function simpleGimme(_ss, _root)
		{
			_root = _root || document.documentElement;
			var counter = 0;
			var i, len;
			var elems, inProps = _ss.inProps, exProps = _ss.exProps;
			if (inProps.id)
			{
				elems = [ document.getElementById(inProps.id) ];
				delete inProps.id;
			}
			else
			{
				// TODO:  this should be anywhere in the pseudos, not just the last part -- must fix
				var p = inProps.pseudos[inProps.pseudos.length - 1];
				if (p && p.name === 'nth-child')
				{
					elems = buildNthArray(p.param.a, p.param.b, inProps.tag);
					inProps.pseudos = inProps.pseudos.splice(inProps.pseudos.length, 1);
				}
				else
				{
					elems = _root.getElementsByTagName(inProps.tag || '*');
				}

				if (exProps.isEmpty
					&& inProps.classes.length < 1
					&& inProps.attributes.length < 1
					&& inProps.pseudos.length < 1)
				{
					return makeArray(elems);
				}
				delete inProps.tag;
			}

			var matched = [];
			len = elems.length;
			for (i = 0; i < len; i++)
			{
				if (Engine.processSelector(_ss, elems[i]))
				{
					matched[counter++] = elems[i];
				}
			}

			return matched;
		}

		function filterElements(_elems, _ss)
		{
			var matched = []
			,counter = 0
			,len = _elems.length
			,elem
			,i;

			for (i = 0; i < len; i++)
			{
				elem = _elems[i];
				if (elem.nodeType !== 1)
				{
					continue;
				}

				if (processSelector(_ss, elem))
				{
					matched[counter++] = elem;
				}
			}

			return matched;
		}

		function makeArray(_enumerable)
		{
			var matched = []
			,counter = 0
			,len = _enumerable.length
			,i;

			for (i = 0; i < len; i++)
			{
				matched[counter++] = _enumerable[i];
			}
			return matched;
		}

		function buildNthArray(_a, _b, _tag)
		{
			return nthHashHash[_a + 'n+' + _b] = nth(_a, _b, _tag, true);
		}

		function buildNthHash(_a, _b)
		{
			return nthHashHash[_a + 'n+' + _b] = nth(_a, _b, null, false);
		}

		function nth(_a, _b, _tag, _includeElement)
		{
			if (_tag === '*')
			{
				_tag = null;
			}

			var all = document.getElementsByTagName('*')
			,len = all.length
			,sibHash = {}
			,count = 0
			,uid
			,elem
			,i
			,x;

			var matched = _includeElement ? [] : {};
			for (i = 0; i < len; i++)
			{
				elem = all[i];
				uid = getObjectGUID(elem.parentNode);
				sibHash[uid] = sibHash[uid] || 0;									// sibHash[uid] keeps count of siblings... if uninitialized, it will be given a value of 0
				x = (count = ++sibHash[uid]) - _b;									// FIRST, count and sibHash[uid] both get incremented, and THEN, x = count - b

				if (_a === 0 && (_tag && elem.tagName !== _tag ? false : true))
				{
					if (x === 0)
					{
						if (_includeElement)
						{
							matched.push(elem);
						}
						matched[getObjectGUID(elem)] = true;
					}
				}
				else if ((_a * x >= 0) && (x % _a === 0) && (_tag && elem.tagName !== _tag ? false : true))
				{
					if (_includeElement)
					{
						matched.push(elem);
					}
					matched[getObjectGUID(elem)] = true;
				}
			}

			return matched;
		}

		function nthCacheContains(_a, _b, _elemGUID)
		{
			var nthKey = _a + 'n+' + _b;
			var h = nthHashHash[nthKey];
			if (!h)
			{
				h = nthHashHash[nthKey] = buildNthHash(_a, _b);
			}
			return h[_elemGUID] === true;
		}
	};

	
	// the css parser
	var CSSParser = new function()
	{
		var rules = null
		,normalizingPattern = null
		,combinators = '';

		this.setRules = function(_rules)
		{
			rules = _rules;

			var symbol;
			for (symbol in rules.combinator)
			{
				if (rules.combinator.hasOwnProperty(symbol))
				{
					combinators += symbol;
				}
			}
			normalizingPattern = new RegExp('\\s*([' + combinators + '])\\s*', 'g');
		};

		this.parseSelector = function(_s)
		{
			// Trim beginning and ending white space, and remove space from around combinators
			_s = trim(_s).replace(normalizingPattern, '$1');

			var rule = null
			,selectors = []
			,simpleSelector = new SimpleSelector();

			var hints = { anchor: new SelectorAnchor(), isPartialQuery: false, initialCollection: null }
			,currentAnchor = hints.anchor
			,previousAnchor = currentAnchor
			,anchorElem;

			var startSkip
			,endSkip
			,startModifier
			,endModifier
			,endsWith;

			var c, i = 0, part = '', len = _s.length;
			while (i < len)
			{
				c = _s.charAt(i);
				rule = rule || selectorRules.inRules[c] || selectorRules.exRules[c];
				if (rule)
				{
					endsWith = rule.endsWith + (rule.stopForCombinators === false ? '' : combinators);
					startSkip = rule.startSkip || 0;
					endSkip = rule.endSkip || 0;
					startModifier = rule.startModifier || null;
					endModifier = rule.endModifier || null;

					c = _s.charAt(i += startSkip);
					while (endsWith.indexOf(c) === -1)
					{
						if (c === startModifier)
						{
							while (c && c !== endModifier)
							{
								part += c;
								c = _s.charAt(++i);
							}
						}
						part += c;
						c = _s.charAt(++i);
					}

					if ((anchorElem = simpleSelector.addPart(part, rule)) !== -1)
					{
						previousAnchor = currentAnchor;
						currentAnchor = new SelectorAnchor(selectors.length, anchorElem);
					}

					i += endSkip;
					part = '';
					rule = null;
					continue;
				}
				else
				{
					if (combinators.indexOf(c) !== -1)
					{
						// An anchor that immediately preceeds a sibling-type combinator (+ or ~) is problematic and needs to be reverted (currentAnchor = previousAnchor).
						// If the combinator, c, was /not/ sibling-type, "lock in" the currentAnchor (previousAnchor = currentAnchor).
						(c === '+' || c === '~') ? currentAnchor = previousAnchor : previousAnchor = currentAnchor;

						if (!(hints.isPartialQuery = i === 0))
						{
							selectors.push(simpleSelector);
							simpleSelector = new SimpleSelector();
						}
						selectors.push(c);

						// HACK:  this is a total hack, but it yields a speed boost on certain direct-child queries
						hints.initialCollection = (c === '>' && currentAnchor.elem && currentAnchor.elem !== -1 && currentAnchor.index === selectors.length - 2) ? currentAnchor.elem.childNodes : null;
					}
					else
					{
						rule = selectorRules.inRules['tag'];
						continue;
					}
				}

				i++;
			}

			selectors.push(simpleSelector);
			currentAnchor.isIdeal = currentAnchor.elem !== -1 && selectors.length - 3 === currentAnchor.index;
			hints.anchor = currentAnchor;
			return { selectors: selectors, hints: hints };
		};

		this.parseAttribute = function(_attr)
		{
			var pattern = /\=|\^=|\$=|\*=|\|=|~=|!=/;
			var delim = _attr.match(pattern);
			delim = delim && delim[0];
			var avPair = _attr.split(delim);
			var attr = avPair[0] && trim(avPair[0].replace(/[\[\]"]/g, ''));
			var val = avPair[1] && trim(avPair[1].replace(/[\[\]"]/g, ''));
			return { name: attr, delim: delim, val: val };
		};

		this.parsePseudo = function(_pseudo)
		{
			var pattern = /(.*)\((.*)\)/;
			var m = _pseudo.match(pattern);
			var name = (m && m[1]) || _pseudo;
			var param = m && m[2];
			var a, b, n;
			if (name === 'nth-child')
			{
				if (param === 'even')
				{
					param = { wholeValue: param, a: 2, b: 0 };
				}
				else if (param === 'odd')
				{
					param = { wholeValue: param, a: 2, b: 1 };
				}
				else
				{
					// valid patterns are in the form an+b,
					// where a is 1 if unspecified
					// where b is 0 if unspecified
					// where n is 0 if unspecified
					// a and b can be either positive or negative (specifying + for positive is optional)
					pattern = /([+-]?\d+)?(n)?([+-]?\d+)?/;

					// match it
					m = param.match(pattern);

					a = parseInt(m[1]) || 1;                  // if a is unspecified, default to 1
					n = m[2] ? 1 : 0;	                      // if n is absent from the string, default it to 0
					b = a && !n ? a : parseInt(m[3]) || 0;    // if b is unspecified, default to 0

					param = { wholeValue: param, a: a * n, b: b };
				}
			}
			return { name: name, param: param };
		};

		this.createReferenceSelector = function(_ref)
		{
			return new SimpleSelector(_ref);
		};

		// SimpleSelector Class
		function SimpleSelector(_ref)
		{
			this.cursor = null;
			this.refersTo = _ref;
			this.inProps =
			{
				id: null
				,tag: null
				,classes: []
				,attributes: []
				,pseudos: []
			};

			this.exProps =
			{
				isEmpty: true
			};

			var key, rule, exRules = selectorRules.exRules;
			for (key in exRules)
			{
				if (exRules.hasOwnProperty(key))
				{
					delete this.exProps.isEmpty;
					rule = exRules[key];
					switch (rule.objType)
					{
						case 'array':
							this.exProps[rule.name] = [];
							break;

						case 'null':
						default:
							this.exProps[rule.name] = null;
							break;
					}
				}
			}
		}
		SimpleSelector.prototype.addPart = function(_part, _rule)
		{
			var name = _rule.name
			,preFn = _rule.preFn
			,hintFn = _rule.hintFn
			,hint = -1;

			if (typeof preFn === 'function')
			{
				_part = preFn(_part);
			}

			var thing = typeof this.inProps[name] !== 'undefined' ? this.inProps : this.exProps;
			thing[name] instanceof Array ? thing[name].push(_part) : thing[name] = _part;

			if (typeof hintFn === 'function')
			{
				hint = hintFn(_part);
			}

			return hint;
		};
		// End SimpleSelector Class

		// SelectorAnchor Class
		function SelectorAnchor(_idx, _elem, _isIdeal)
		{
			this.index = _idx === 0 ? 0 : _idx || NaN;
			this.elem = _elem  === null ? null : (_elem || -1);
			this.isIdeal = _isIdeal || false;
		}
		// End SelectorAnchor Class
	};


	// internal rules for selectors and combinators
	var selectorRules =
	{
		inRules:
		{
			'tag':
			{
				name: 'tag'
				,endsWith: '#.[:'
				,preFn: function(_part) { return _part.toUpperCase(); }
				,hintFn: function(_part) { return _part === 'BODY' || _part === 'HTML' ? document.getElementsByTagName(_part)[0] : -1; }
				,processFn: function(_elem, _tag)
				{
					return _elem.tagName === _tag;
				}
			}

			,'#':
			{
				name: 'id'
				,endsWith: '#.[:'
				,startSkip: 1
				,preFn: null
				,hintFn: function(_part)
				{
					return document.getElementById(_part);
				}
				,processFn: function(_elem, _id)
				{
					return _elem.id === _id;
				}
			}

			,'.':
			{
				name: 'classes'
				,endsWith: '.[:'
				,startSkip: 1
				,objType: 'array'
				,processFn: function(_elem, _classes)
				{
					var className = _elem.className;
					if (typeof className !== 'string')
					{
						className = _elem.getAttribute('class');
					}
	                
					if (!className || className.length < 1)
					{
						return false;
					}

					var elemClass = ' ' + className + ' ';
					var i, len = _classes.length;
					for (i = 0; i < len; i++)
					{
						if (elemClass.indexOf(' ' + _classes[i] + ' ') === -1)
						{
							return false;
						}
					}
	                return true;
				}
			}

			,'[':
			{
				name: 'attributes'
				,endsWith: ']'
				,stopForCombinators: false
				,startSkip: 1
				,endSkip: 1
				,startModifier: '"'
				,endModifier: '"'
				,objType: 'array'
				,preFn: CSSParser.parseAttribute
				,processFn: function(_elem, _attributes)
				{
					var i, len = _attributes.length;
					var attr, attrName, attrVal, attrDelim, elemAttrVal;
					for (i = 0; i < len; i++)
					{
						attr = _attributes[i];
						attrName = attr.name;
						attrVal = attr.val;
						attrDelim = attr.delim;

						elemAttrVal = readAttribute(_elem, attrName);
						if (elemAttrVal === null)
						{
							return false;
						}
						else if (!attrDelim)
						{
							continue;
						}
						else if (!selectorRules.attr[attrDelim || 'unknown'](elemAttrVal, attrVal))
						{
							return false;
						}
					}
					return true;
				}
			}

			,':':
			{
				name: 'pseudos'
				,endsWith: '#.[:)'
				,startSkip: 1
				,startModifier: '('
				,endModifier: ')'
				,objType: 'array'
				,preFn: CSSParser.parsePseudo
				,processFn: function(_elem, _pseudos)
				{
					var pseudo
					,fn
					,len = _pseudos.length
					,i;

					for (i = 0; i < len; i++)
					{
						pseudo = _pseudos[i];
						fn = selectorRules.pseudo[pseudo.name] || selectorRules.pseudo.unknown;
						if (!fn(_elem, pseudo))
						{
							return false;
						}
					}
					return true;
				}
			}
		}

		,exRules:
		{
		}

		,attr:
		{
			'=': function(_attr, _val)
			{
				return _attr === _val;
			}

			,'^=': function(_attr, _val)
			{
				return _attr.indexOf(_val) === 0
			}

			,'$=': function(_attr, _val)
			{
				var lio = _attr.lastIndexOf(_val);
				return lio !== -1 && lio + _val.length === _attr.length;
			}

			,'*=': function(_attr, _val)
			{
				return _attr.indexOf(_val) !== -1;
			}

			,'|=': function(_attr, _val)
			{
				return (_attr === _val) || (_attr.indexOf(_val + '-') === 0);
			}

			,'~=': function(_attr, _val)
			{
				return (_attr === _val) || contains(_attr.split(' '), _val);
			}

			,'!=': function(_attr, _val)
			{
				return _attr !== _val;
			}

			,'unknown': function()
			{
				return false;
			}
		}

		,pseudo:
		{
			'first-child': child('previous')
			,'last-child': child('next')
			,'only-child': function(_elem)
			{
				return selectorRules.pseudo['first-child'](_elem) && selectorRules.pseudo['last-child'](_elem);
			}

			,'nth-child': function(_elem, _pseudo)
			{
				var a = _pseudo.param.a;
				var b = _pseudo.param.b;

				// check the an+b cache for _elem
				return Engine.nthCacheContains(a, b, getObjectGUID(_elem));
			}

			,'contains': function(_elem, _pseudo)
			{
				return _elem.innerHTML.indexOf(_pseudo.param) !== -1;
			}

			,'unknown': function()
			{
				return false;
			}
		}

		,combinator:
		{
			' ': function (_lhs, _rhs)
			{
				var p = _rhs.parentNode;
				while (p && p !== document)
				{
					if (Engine.processSelector(_lhs, p))
					{
						_lhs.cursor = p;
						return true;
					}
					p = p.parentNode;
				}

				return false;
			}

			,'>': function(_lhs, _rhs)
			{
				var p = _rhs.parentNode;
				_lhs.cursor = p;
				return Engine.processSelector(_lhs, p);
			}
			
			,'<': function(_lhs, _rhs)
			{
				var children = _rhs.childNodes;
				var i, len = children.length;
				for (i = 0; i < len; i++)
				{
					if (Engine.processSelector(_lhs, children[i]))
					{
						return true;
					}
				}
				return false;
			}

			,'~': function(_lhs, _rhs)
			{
				var prevSib = _rhs.previousSibling;
				while (prevSib)
				{
					if (prevSib.nodeType === 1 && Engine.processSelector(_lhs, prevSib))
					{
						_lhs.cursor = prevSib;
						return true;
					}
					else
					{
						prevSib = prevSib.previousSibling;
					}
				}
				return false;
			}

			,'+': function(_lhs, _rhs)
			{
				var prevSib = _rhs.previousSibling;
				while (prevSib && prevSib.nodeType !== 1)
				{
					prevSib = prevSib.previousSibling;
				}
				_lhs.cursor = prevSib;
				return Engine.processSelector(_lhs, prevSib);
			}

			,',': function()
			{
				return false;  // Not yet supported
			}
		}
	};


	// selector extension functions
	Gimme.Selectors =
	{
		addRule: function(_symbol, _rule)
		{
			selectorRules.exRules[_symbol] = _rule;
		}

		,addPseudo: function(_name, _fn)
		{
			selectorRules.pseudo[_name] = _fn;
		}

		,addAttribute: function(_name, _fn)
		{
			selectorRules.attr[_name] = _fn;
		}
		
		,addCombinator: function(_symbol, _fn)
		{
			selectorRules.combinator[_symbol] = _fn;
		}
	};


	// initialize the parser with a set of rules
	CSSParser.setRules(selectorRules);

	// the public function for querying
	Gimme.query = Engine.query;

	// shortcut for DOM element retrieval by ID
	Gimme.id = function(_id) { return document.getElementById(_id); };


	// ------------------------------------------------
	// the gimme globals space starts here
	// ------------------------------------------------

	// for those unfortunate times when we simply /must/ write browser specific code
	var ua = navigator.userAgent.toLowerCase();
	Gimme.Browser =
	{
		isIE: typeof ActiveXObject !== 'undefined'
		,isOpera: typeof window.opera !== 'undefined'
		,isKHTML: ua.indexOf('khtml') !== -1
		,isGecko: ua.indexOf('khtml') === -1 && ua.indexOf('gecko') !== -1
		,isInIFrame: function()
		{
			try
			{
				return window.frameElement && window.frameElement.tagName === 'IFRAME';
			}
			catch (oops)
			{
				return true;		// iframes are more common than framesets (this is a tradeoff as it will yield the wrong value in cases of frameset only)
			}
		}()
		,isInFrameset: window != top		// can't use the identity operator here (IE gets it wrong)
		,isInQuirksMode: document.compatMode === 'BackCompat'
		,offsetIncludesBorders: function()
		{
		    if (typeof this.value === 'undefined')
		    {
				var a = document.createElement('div');
				a.setAttribute('style', 'position:absolute;visibility:hidden;top:0;left:0;border:1px solid #000;');
				var b = document.createElement('div');			
				a.appendChild(b);
				document.body.appendChild(a);
				this.value = offsetIncludesBorders = b.offsetTop === 1;
				document.body.removeChild(a);
				a = b = null;
		    }
		    return this.value;
		}
	};

	// a little organization for when adding expando properties to DOM elements is necessary
	var expando =
	{
		guid: '_$gimme$_guid'
		,descendant: '_$gimme$_descendant'
	};

	var g_GUIDCounter = 0;
	function getObjectGUID(_elem)
	{
		if (_elem === window)
		{
			return 'theWindow';
		}
		else if (_elem === document)
		{
			return 'theDocument';
		}
		else if (typeof _elem.uniqueID !== 'undefined')
		{
			return _elem.uniqueID;
		}

		var ex = expando.guid;
		if (typeof _elem[ex] === 'undefined')
		{
			_elem[ex] = ex + g_GUIDCounter++;
		}
		return _elem[ex];
	}

	var indexOf = function()
	{
		return typeof Array.prototype.indexOf !== 'undefined' ? native_indexOf : custom_indexOf;

		function native_indexOf(_arr, _item)
		{
			return _arr.indexOf(_item);
		}

		function custom_indexOf(_arr, _item)
		{
			var i, len = _arr.length;
			for (i = 0; i < len; i++)
			{
				if (_arr[i] === _item)
				{
					return i;
				}
			}
			return -1;
		}
	}();

	function contains(_arr, _item)
	{
		return indexOf(_arr, _item) !== -1;
	}

	function trim(_str)
	{
		return _str.replace(/^\s+|\s+$/g, '');
	}

	function normalize(_str)
	{
		return trim(_str).replace(/\s{2,}/g, ' ');
	}

	var readAttribute = function()
	{
		return Gimme.Browser.isIE ? ie_readAttribute : w3c_readAttribute;

		function w3c_readAttribute(_elem, _attr)
		{
			return _elem.getAttribute(_attr);
		}

		function ie_readAttribute(_elem, _attr)
		{
			switch (_attr.toLowerCase())
			{
				case 'class':
					return _elem.className || null;

				case 'id':
					return _elem.id || null;

				case 'href':
				case 'src':
					// IE Bug: when a '#default#userData' behavior is bound to an element, getAttribute fails to return anything useful.
					// Worse, it throws a run-time error if you try to invoke it with an iFlag parameter, as in: .getAttribute('href', 2);
					// Even more odd, typeof .getAttribute in this case returns "unknown" ( ?? what the heck! )
					// We can leverage this oddity, to avoid invoking .getAttribute(..) with an iFlag parameter.  In that case, we fall back
					// to trying to retrieve the attribute value through the element's .attributes collection.
					if (typeof _elem.getAttribute !== 'undefined')
					{
						// If typeof .getAttribute is not "unknown" we can safely use iFlag parameters when invoking .getAttribute(..).
						// It still might return null though, (in IE) so a series of logical ORs will help increase our chances of
						// retrieving the desired attribute value.
						return _elem.getAttribute(_attr, 2);
					}
					break;

				default:
					break;
			}

			return _elem.attributes && _elem.attributes[_attr] ? _elem.attributes[_attr].nodeValue : _elem.getAttribute(_attr);
		}
	}();

	var attrExists = function()
	{
		return typeof document.createElement('div').hasAttribute !== 'undefined' ? w3c_attrExists : ie_attrExists;

		function w3c_attrExists(_elem, _attr)
		{
			return _elem.hasAttribute(_attr);
		}

		function ie_attrExists(_elem, _attr)
		{
			return !!readAttribute(_elem, _attr);
		}
	}();

	function child(_direction)
	{
		var sibling = _direction + 'Sibling';

		return function(_elem)
		{
			var sib = _elem[sibling];
			while (sib && sib.nodeType !== 1)
			{
				sib = sib[sibling];
			}
			return !sib;
		}
	}
	
	function convertToPixels(_str, _context)
	{
		// if str is "false-like", treat it as 0
		if (!_str) { return 0; }

		// if the units are already pixels, no conversion is necessary, just parseInt and return
		if (/px$/.test(_str)) { return parseInt(_str, 10); }
		
		// if no _context was specified, default to the <body>
		if (!_context) { _context = document.body; }
		
		// If we get here, we've got some work to do:
		// 1. Create an invisible div that we'll use to measure the style represented by _str
		// 2. Make sure its position is absolute so it doesn't shift other elements on the page
		// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
		var tmp = document.createElement('div');
		tmp.style.visbility = 'hidden';
		tmp.style.position = 'absolute';				
		tmp.style.lineHeight = '0';				
		
		// Two additional checks are needed before we conver to pixels
		// 1.  If we're tyring to convert a % to pixels, we have to do it in the context of a parent element
		// 2.  Or if the _context element is an <img>, we also need to use the parent element, b/c we're going to append the
		// invisible div (tmp) to the _context, but it's not valid to append an element to an <img> tag
		if (/%$/.test(_str) || _context.tagName === 'IMG')
		{
			_context = _context.parentNode || _context;
			tmp.style.height = _str;
		}
		else
		{
			tmp.style.borderStyle = 'solid';
			tmp.style.borderBottomWidth = '0';					
			tmp.style.borderTopWidth = _str;
		}
		
		_context.appendChild(tmp);
		var px = tmp.offsetHeight;
		_context.removeChild(tmp);
		
		return px || 0;	
	}

	// the functions defined above have usefulness outside of the this closure, so we expose them here under the "Helper" moniker.
	Gimme.Helper =
	{
		getObjectGUID: getObjectGUID
		,indexOf: indexOf
		,contains: contains
		,trim: trim
		,normalize: normalize
		,hasClass: selectorRules.inRules['.'].processFn
		,readAttribute: readAttribute
		,attrExists: attrExists
		,convertToPixels: convertToPixels
	};

})();

